﻿using System;
using System.Collections.Generic;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using Kodefabrikken.DDD.Entity;
using Kodefabrikken.DDD.Entity.Experimental;

namespace Kodefabrikken.DDD.Entity.UnitTest
{
    [TestClass]
    public class IdentityBehaviour
    {
        // Identity types to test with
        private class IdentityOne<T> : Identity<T, IdentityOne<T>>
        {
            public IdentityOne(T id)
                : base(id)
            {
            }
        }

        private class IdentityTwo<T> : Identity<T, IdentityTwo<T>>
        {
            public IdentityTwo(T id)
                : base(id)
            {
            }
        }

        private class IdentityOneSubClass<T> : IdentityOne<T>
        {
            public IdentityOneSubClass(T id)
                : base(id)
            {
            }
        }


        [TestClass]
        public class ctor
        {
            // An invalid identity type definition
            private class BadGenericParameterToIdentityClass<T> : Identity<T, IdentityTwo<T>>
            {
                public BadGenericParameterToIdentityClass(T id)
                    : base(id)
                {
                }
            }

            [TestMethod]
            [ExpectedException(typeof(Exception))]
            public void ShouldThrowExceptionWhenIdentityClassIsGivenDifferentSubClassType()
            {
                BadGenericParameterToIdentityClass<int> ic = new BadGenericParameterToIdentityClass<int>(666);
            }
        }

        [TestClass]
        public class Value
        {
            [TestMethod]
            public void ShouldBeEqualToInitializedId()
            {
                int testValue = 7;

                IdentityOne<int> obj = new IdentityOne<int>(testValue);

                Assert.AreEqual(testValue, obj.Value);
            }
        }

        [TestClass]
        public class Value_IIdentity
        {
            [TestMethod]
            public void ShouldBeEqualToInitializedId()
            {
                int testValue = 7;

                IIdentity<int> obj = new IdentityOne<int>(testValue);

                Assert.AreEqual(testValue, obj.Value);
            }
        }

        [TestClass]
        public class Equals_object
        {
            [TestMethod]
            public void ShouldBeTrueForIdentitiesWithSameId()
            {
                int testValue = 3;

                object obj1 = new IdentityOne<int>(testValue);
                object obj2 = new IdentityOne<int>(testValue);

                Assert.IsTrue(obj1.Equals(obj2));
            }

            [TestMethod]
            public void ShouldBeFalseForIdentitiesWithDifferentIds()
            {
                int testValue1 = 3;
                int testValue2 = 7;
                Assert.AreNotEqual(testValue1, testValue2);

                object obj1 = new IdentityOne<int>(testValue1);
                object obj2 = new IdentityOne<int>(testValue2);

                Assert.IsFalse(obj1.Equals(obj2));
            }

            [TestMethod]
            public void ShouldBeFalseForDifferentIdentitiesEvenIfSameId()
            {
                int testValue = 3;

                object obj1 = new IdentityOne<int>(testValue);
                object obj2 = new IdentityTwo<int>(testValue);

                Assert.IsFalse(obj1.Equals(obj2));
            }

            [TestMethod]
            public void ShouldBeFalseForIdentitiesWithSameSuperClassButDifferentSubClassEvenIfSameId()
            {
                int testValue = 3;

                object obj1 = new IdentityOne<int>(testValue);
                object obj2 = new IdentityOneSubClass<int>(testValue);

                Assert.IsFalse(obj2.Equals(obj1));
                Assert.IsFalse(obj1.Equals(obj2));
            }
        }

        [TestClass]
        public class Equals_IEquatable
        {
            [TestMethod]
            public void ShouldBeTrueForIdentitiesWithSameId()
            {
                int testValue = 3;

                IIdentity<int> obj1 = new IdentityOne<int>(testValue);
                IIdentity<int> obj2 = new IdentityOne<int>(testValue);

                Assert.IsTrue(obj1.Equals(obj2));
            }

            [TestMethod]
            public void ShouldBeFalseForIdentitiesWithDifferentIds()
            {
                int testValue1 = 3;
                int testValue2 = 7;
                Assert.AreNotEqual(testValue1, testValue2);

                IIdentity<int> obj1 = new IdentityOne<int>(testValue1);
                IIdentity<int> obj2 = new IdentityOne<int>(testValue2);

                Assert.IsFalse(obj1.Equals(obj2));
            }

            [TestMethod]
            public void ShouldBeFalseForDifferentIdentitiesEvenIfSameId()
            {
                int testValue = 3;

                IIdentity<int> obj1 = new IdentityOne<int>(testValue);
                IIdentity<int> obj2 = new IdentityTwo<int>(testValue);

                Assert.IsFalse(obj2.Equals(obj1));
            }

            [TestMethod]
            public void ShouldBeFalseForIdentitiesWithSameSuperClassButDifferentSubClassEvenIfSameId()
            {
                int testValue = 3;

                IIdentity<int> obj1 = new IdentityOne<int>(testValue);
                IIdentity<int> obj2 = new IdentityOneSubClass<int>(testValue);

                Assert.IsFalse(obj2.Equals(obj1));
                Assert.IsFalse(obj1.Equals(obj2));
            }
        }

        [TestClass]
        public class operator_Equal
        {
            [TestMethod]
            public void ShouldBeTrueForIdentitiesWithSameId()
            {
                int testValue = 3;

                IdentityOne<int> obj1 = new IdentityOne<int>(testValue);
                IdentityOne<int> obj2 = new IdentityOne<int>(testValue);

                Assert.IsTrue(obj1 == obj2);
            }

            [TestMethod]
            public void ShouldBeFalseForIdentitiesWithDifferentIds()
            {
                int testValue1 = 3;
                int testValue2 = 7;
                Assert.AreNotEqual(testValue1, testValue2);

                IdentityOne<int> obj1 = new IdentityOne<int>(testValue1);
                IdentityOne<int> obj2 = new IdentityOne<int>(testValue2);

                Assert.IsFalse(obj1 == obj2);
            }

            [TestMethod]
            public void ShouldBeFalseForIdentitiesWithSameSuperClassButDifferentSubClassEvenIfSameId()
            {
                int testValue = 3;

                IdentityOne<int> obj1 = new IdentityOne<int>(testValue);
                IdentityOneSubClass<int> obj2 = new IdentityOneSubClass<int>(testValue);

                Assert.IsFalse(obj1 == obj2);
                Assert.IsFalse(obj2 == obj1);
            }
        }

        [TestClass]
        public class operator_NotEqual
        {
            [TestMethod]
            public void ShouldBeFalseForIdentitiesWithSameId()
            {
                int testValue = 3;

                IdentityOne<int> obj1 = new IdentityOne<int>(testValue);
                IdentityOne<int> obj2 = new IdentityOne<int>(testValue);

                Assert.IsFalse(obj1 != obj2);
            }

            [TestMethod]
            public void ShouldBeTrueForIdentitiesWithDifferentIds()
            {
                int testValue1 = 3;
                int testValue2 = 7;
                Assert.AreNotEqual(testValue1, testValue2);

                IdentityOne<int> obj1 = new IdentityOne<int>(testValue1);
                IdentityOne<int> obj2 = new IdentityOne<int>(testValue2);

                Assert.IsTrue(obj1 != obj2);
            }
        }

        [TestMethod]
        public void EqualsShouldReturnTrueForIdentitiesWithSameSubClassButDifferentObjectPointers()
        {
            int testValue = 3;

            IdentityOne<int> obj1 = new IdentityOneSubClass<int>(testValue);
            IdentityOneSubClass<int> obj2 = new IdentityOneSubClass<int>(testValue);

            Assert.IsTrue(obj2.Equals(obj1));
            Assert.IsTrue(obj2.Equals((object)obj1));
            Assert.IsTrue(obj1.Equals(obj2));
            Assert.IsTrue(obj1.Equals((object)obj2));
        }

        [TestMethod]
        public void ListContainsShouldFindObject()
        {
            int testValue = 7;
            List<IdentityOne<int>> list = new List<IdentityOne<int>>() { new IdentityOne<int>(3), new IdentityOne<int>(testValue) };

            Assert.IsTrue(list.Contains(new IdentityOne<int>(testValue)));
        }

        [TestMethod]
        public void ListContainsShouldFindObjectWhenListContainsIdentityInterfaces()
        {
            int testValue = 7;
            List<IIdentity<int>> list = new List<IIdentity<int>>() { new IdentityOne<int>(3), new IdentityOne<int>(testValue) };

            Assert.IsTrue(list.Contains(new IdentityOne<int>(testValue)));
        }
    }
}
